/*
 *
 *  Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.
 *  SPDX-License-Identifier: Apache-2.0
 *
 */
import {
  Button,
  Form,
  Input,
  Justify,
  Layout,
  Modal,
  SearchBox,
  Select,
  Table,
  TableColumn,
  Text,
  TextArea,
} from 'tea-component';
import React, { Ref, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import {
  ChainContractMethodsDecoded,
  InvokeRecord,
  InvokeRecordListRequest,
  InvokeRecordRequest,
} from '../../common/apis/chains/interface';
import { formatDate } from '../../utils/date';
import {
  useFetchChainContractDetail,
  useFetchInvokeChain,
  useFetchInvokeChainContractList,
  useFetchInvokeRecordList,
  useFetchReInvokeChain,
} from '../../common/apis/chains/hooks';
import { ChainDetailContext } from './chain-detail';
import { autotip, filterable, pageable } from 'tea-component/es/table/addons';
import { omit, PAGE_SIZE_OPTIONS, pick, safeParseArrayStr, tableFilter } from '../../utils/common';
import { useHistory } from 'react-router-dom';
import { Controller, useForm, useWatch } from 'react-hook-form';
import formUtils, { Validator } from '../../utils/form-utils';
import { TextTheme } from '../../common/components/upload-file';
import GlossaryGuide from 'src/common/components/glossary-guide';
import moment from 'moment';
import { splitUrl } from 'src/utils/common';

const { Content } = Layout;
const methodFuncList = [
  {
    text: '调用',
    value: 'invoke',
  },
  {
    text: '查询',
    value: 'query',
  },
];

function renderTxId(record: InvokeRecord) {
  const chainMode = String(splitUrl(location.search).get('chainMode') || '');
  const history = useHistory();
  const { chainId } = useContext(ChainDetailContext);
  const handleViewClick = useCallback(() => {
    history.push(`/chains/${chainId}/browser/details?type=deal&txId=${record.TxId}&chainMode=${chainMode}`);
  }, [record]);
  return (
    <Button onClick={handleViewClick} type={'link'} className={'tea-text-overflow'}>
      {record.TxId}
    </Button>
  );
}

const defaultColumns = (onReInvokeChainSuccess: () => void): TableColumn<InvokeRecord>[] => {
  const chainMode = String(splitUrl(location.search).get('chainMode') || '');
  let columns: TableColumn<InvokeRecord>[] = [
    {
      key: 'CreateTime',
      header: '更新时间',
      render: (record: any) => formatDate(record.CreateTime),
    },
    {
      key: 'TxId',
      header: '交易ID',
      width: 300,
      render: renderTxId,
    },
  ];
  if (chainMode !== 'public') {
    columns.push({
      key: 'OrgName',
      header: '发起组织',
    });
  }
  columns = columns.concat([
    {
      key: 'UserName',
      header: '发起用户',
    },
    {
      key: 'ContractName',
      header: '合约',
    },
    {
      key: 'Status',
      header: '上链状态',
      render(record) {
        const item = STATUS_MAP[record.Status];
        return <Text theme={item.theme}>{item.label}</Text>;
      },
    },
    {
      key: 'TxStatus',
      header: '交易状态',
      render(record) {
        const item = TX_STATUS_MAP[record.TxStatus];
        return <Text theme={item.theme}>{item.label}</Text>;
      },
    },
    {
      key: 'Id',
      header: '操作',
      // eslint-disable-next-line react/display-name
      render: (record) => <RecordOptions record={record} onReInvokeChainSuccess={onReInvokeChainSuccess} />,
    },
  ]);
  return columns;
};

const STATUS_MAP: {
  [index: number]: {
    theme: TextTheme;
    label: string;
  };
} = {
  0: {
    theme: 'warning',
    label: '上链中',
  },
  1: {
    theme: 'success',
    label: '已上链',
  },
  2: {
    theme: 'danger',
    label: '上链失败',
  },
};

const TX_STATUS_MAP: {
  [index: number]: {
    theme: TextTheme;
    label: string;
  };
} = {
  0: {
    theme: 'success',
    label: '交易成功',
  },
  1: {
    theme: 'danger',
    label: '交易失败',
  },
};

export default function ChainInvokeContracts() {
  const { chainId } = useContext(ChainDetailContext);
  const [queryParams, setQueryParams] = useState<InvokeRecordListRequest>({
    ChainId: chainId as string,
    PageSize: 10,
    PageNum: 0,
    Status: -1,
    TxStatus: -1,
  });
  const {
    data: { list: records, totalCount },
    run: fetchRecords,
  } = useFetchInvokeRecordList();
  const onSearch = useCallback(
    (value) => {
      setQueryParams({
        ...queryParams,
        TxId: value ? value : undefined,
      });
    },
    [queryParams],
  );
  const [invokeVisible, setInvokeVisible] = useState(false);

  const handleInstallClick = useCallback(() => {
    setInvokeVisible(true);
  }, []);

  useEffect(() => {
    fetchRecords(queryParams);
  }, [queryParams]);
  const columns = useMemo(() => defaultColumns(() => fetchRecords(queryParams)), [queryParams]);

  return (
    <Content>
      <Content.Header title="区块链管理/上链管理" />
      <Content.Body full>
        <Justify
          left={
            <Button type={'primary'} onClick={handleInstallClick}>
              发起上链
            </Button>
          }
          right={
            <SearchBox placeholder="请输入交易哈希搜索" size="l" onSearch={onSearch} onClear={() => onSearch(null)} />
          }
        />
        <Table
          columns={columns}
          records={records}
          className={'tea-mt-5n'}
          recordKey="Id"
          addons={[
            pageable({
              pageSizeOptions: PAGE_SIZE_OPTIONS,
              pageSize: queryParams.PageSize,
              pageIndex: queryParams.PageNum + 1,
              recordCount: totalCount,
              onPagingChange: ({ pageIndex, pageSize }) => {
                setQueryParams({
                  ...queryParams,
                  PageNum: (pageIndex as number) - 1,
                  PageSize: pageSize as number,
                });
              },
            }),
            filterable({
              type: 'single',
              column: 'Status',
              value: queryParams.Status === -1 ? tableFilter.all : String(queryParams.Status),
              onChange: (value) => {
                setQueryParams({
                  ...queryParams,
                  Status: (value === tableFilter.all ? -1 : +value) as InvokeRecord['Status'],
                  PageNum: 0,
                });
              },
              all: {
                value: tableFilter.all,
                text: '全部',
              },
              options: Object.entries(STATUS_MAP).map((item) => ({
                text: item[1].label,
                value: String(item[0]),
              })),
            }),
            filterable({
              type: 'single',
              column: 'TxStatus',
              value: queryParams.TxStatus === -1 ? tableFilter.all : String(queryParams.TxStatus),
              onChange: (value) => {
                setQueryParams({
                  ...queryParams,
                  TxStatus: (value === tableFilter.all ? -1 : +value) as InvokeRecord['TxStatus'],
                  PageNum: 0,
                });
              },
              all: {
                value: tableFilter.all,
                text: '全部',
              },
              options: Object.entries(TX_STATUS_MAP).map((item) => ({
                text: item[1].label,
                value: String(item[0]),
              })),
            }),
            autotip({
              emptyText: '暂无数据',
            }),
          ]}
        />
        <InvokeModal
          visible={invokeVisible}
          onClose={() => setInvokeVisible(false)}
          onSubmit={() => {
            fetchRecords(queryParams);
            setInvokeVisible(false);
          }}
        />
      </Content.Body>
    </Content>
  );
}

function RecordOptions({
  record,
  onReInvokeChainSuccess,
}: {
  record: InvokeRecord;
  onReInvokeChainSuccess: () => void;
}) {
  const chainMode = String(splitUrl(location.search).get('chainMode') || '');
  const history = useHistory();
  const { chainId } = useContext(ChainDetailContext);
  const handleViewClick = useCallback(() => {
    history.push(`/chains/${chainId}/browser/details?type=deal&txId=${record.TxId}&chainMode=${chainMode}`);
  }, [record, chainMode]);
  const { run: reInvokeChain } = useFetchReInvokeChain();

  const handleReInvokeClick = useCallback(async () => {
    await reInvokeChain(record.Id);
    onReInvokeChainSuccess();
  }, [record]);
  return (
    <>
      {[0].includes(record.Status) && <Text theme={'success'}>--</Text>}
      {[1].includes(record.Status) && (
        <Button type={'link'} onClick={handleViewClick}>
          查看
        </Button>
      )}
      {[2].includes(record.Status) && (
        <Button type={'link'} onClick={handleReInvokeClick}>
          重新上链
        </Button>
      )}
    </>
  );
}

/**
 * 发起上链
 */
function InvokeModal(props: { visible: boolean; onClose: () => void; onSubmit: () => void }) {
  const { chainId } = useContext(ChainDetailContext);
  const [paramsReadonly, setParamsReadonly] = useState(false);
  const [paramsShow, setParamsShow] = useState(true);
  const [paramKeys] = useState(['Key', 'Value']);
  const [queryVisible, setQueryVisible] = useState(false);
  const [methodsDecoded, setMethodsDecoded] = useState<ChainContractMethodsDecoded>([]);
  const {
    data: { list: contractList },
    run: fetchContractList,
  } = useFetchInvokeChainContractList();

  const onClose = useCallback(() => {
    // setVisible(false);
    setParamsReadonly(false);
    props.onClose();
    reset({});
  }, [props]);

  const {
    control,
    reset,
    formState: { isValidating, isSubmitted, isValid },
    getValues,
    setValue,
    trigger,
  } = useForm<{
    ContractId: string;
    MethodFunc: 'invoke' | 'query';
    MethodName: string;
    Parameters:
      | {
          Key: string;
          Value: string;
        }[]
      | undefined;
  }>({
    mode: 'onBlur',
  });
  const { run, loading, queryResult } = useFetchInvokeChain();
  const { fetch: fetchContractDetail, detail: contractDetail } = useFetchChainContractDetail();
  const customMethodNameInputRef = useRef<any>();
  const paramsInputRef = useRef<any>();
  const onSubmit = useCallback(async () => {
    const params = omit(getValues(), ['ContractId']);
    if (params.Parameters) {
      params.Parameters = params.Parameters.filter((item: { [x: string]: string }) => !!item[paramKeys[0]]);
    }
    await run({
      ...params,
      ContractName: contractDetail!.ContractName,
      MethodFunc: params.MethodFunc,
      MethodName:
        getValues('MethodName') === '_custom_' ? customMethodNameInputRef.current.value : getValues('MethodName'),
      ChainId: chainId as string,
    });
    // setVisible(false);
    props.onClose();
    props.onSubmit();
    setMethodsDecoded([]);
    reset({});
  }, [contractDetail, paramKeys]);
  const contractId = useWatch({
    control,
    name: 'ContractId',
  });
  const methodFunc = useWatch({
    control,
    name: 'MethodFunc',
  });
  const methodName = useWatch({
    control,
    name: 'MethodName',
  });

  const methodsDecodedOptions = useMemo(
    () => methodsDecoded.filter((item) => item.MethodFunc === methodFunc),
    [methodFunc, methodsDecoded],
  );
  useEffect(() => {
    fetchContractList(chainId as string);
  }, []);
  useEffect(() => {
    if (contractDetail) {
      setMethodsDecoded(safeParseArrayStr(contractDetail.Methods));
    }
  }, [contractDetail]);

  useEffect(() => {
    if (contractId?.match(/\d+/)) {
      fetchContractDetail({
        Id: +contractId,
      });
    }
  }, [contractId]);
  useEffect(() => {
    if (props.visible) {
      setValue('MethodFunc', 'invoke');
    }
  }, [props.visible]);

  useEffect(() => {
    setValue('MethodName', '');
  }, [methodFunc]);

  useEffect(() => {
    if (methodName !== '_custom_') {
      setParamsReadonly(true);
      const find = methodsDecoded.find((item) => item.MethodName === methodName);
      if (find) {
        const params = find.MethodKey.split(',').map((item) => ({
          Key: item,
          Value: '',
        }));
        setValue('Parameters', params);
        paramsInputRef.current?.updateParams(params);
        setParamsShow(!!find.MethodKey);
      }
    } else {
      trigger('MethodName');
      setValue('Parameters', []);
      paramsInputRef.current?.updateParams([]);
      setParamsReadonly(false);
      setParamsShow(true);
    }
  }, [methodName, methodsDecoded]);

  useEffect(() => {
    setQueryVisible(!!queryResult?.ContractResult);
  }, [queryResult]);

  return (
    <>
      <Modal visible={props.visible} caption="发起上链" onClose={onClose}>
        <Modal.Body>
          <Form>
            <Controller
              control={control}
              rules={{
                required: '请选择合约',
              }}
              name="ContractId"
              render={({ field, fieldState }) => (
                <Form.Item
                  status={formUtils.getStatus({
                    fieldState,
                    isValidating,
                    isSubmitted,
                  })}
                  required
                  label="选择合约"
                  message={fieldState.error?.message}
                >
                  <Select
                    options={contractList.map((item) => ({
                      text: item.ContractName,
                      value: String(item.ContractId),
                    }))}
                    {...field}
                  />{' '}
                </Form.Item>
              )}
            />
            <Controller
              control={control}
              rules={{
                required: '请选择交互类型',
              }}
              name="MethodFunc"
              render={({ field, fieldState }) => (
                <Form.Item
                  status={formUtils.getStatus({
                    fieldState,
                    isValidating,
                    isSubmitted,
                  })}
                  required
                  label={<GlossaryGuide title={'交互类型'} />}
                  message={fieldState.error?.message}
                >
                  <Select options={methodFuncList} {...field} />{' '}
                </Form.Item>
              )}
            />
            <Controller
              control={control}
              name="MethodName"
              rules={{
                validate: (value) => {
                  if (value === '_custom_') {
                    return Validator.validateContractFuncName(customMethodNameInputRef.current.value);
                  }
                },
              }}
              render={({ field, fieldState }) => (
                <>
                  <Form.Item
                    status={formUtils.getStatus({
                      fieldState,
                      isValidating,
                      isSubmitted,
                    })}
                    required
                    label={<GlossaryGuide title={'调用方法'} />}
                    message={fieldState.error?.message}
                  >
                    <Select
                      {...field}
                      options={[
                        ...methodsDecodedOptions.map((item) => ({
                          text: item.MethodName,
                          value: item.MethodName,
                        })),
                        {
                          text: '自定义',
                          value: '_custom_',
                        },
                      ]}
                    />
                    {getValues('MethodName') === '_custom_' && (
                      <Input
                        ref={customMethodNameInputRef}
                        className="content-mt-2n"
                        autoComplete="off"
                        placeholder={'请输入合约调用方法'}
                        onBlur={() => {
                          trigger('MethodName');
                        }}
                      />
                    )}
                  </Form.Item>
                </>
              )}
            />
            {paramsShow && <Form.Item label={<GlossaryGuide title={'参数'} />}></Form.Item>}
          </Form>
          {paramsShow && (
            <div>
              <Controller
                control={control}
                name="Parameters"
                render={({ field }) => (
                  <ChainParamsInput
                    onChange={(params: InvokeRecordRequest['Parameters']) => setValue(field.name, params)}
                    readonly={paramsReadonly}
                    defaultValues={getValues('Parameters')}
                    paramKeys={paramKeys}
                    ref={paramsInputRef}
                  />
                )}
              />
            </div>
          )}
        </Modal.Body>
        <Modal.Footer>
          <Button type="primary" onClick={onSubmit} disabled={!isValid} loading={loading}>
            确定
          </Button>
          <Button type="weak" onClick={onClose}>
            取消
          </Button>
        </Modal.Footer>
      </Modal>
      <Modal caption="查询结果" visible={queryVisible} onClose={() => setQueryVisible(false)}>
        <Modal.Body>
          <TextArea readOnly size="full" rows={20} value={queryResult?.ContractResult} />
        </Modal.Body>
        <Modal.Footer>
          <Button onClick={() => setQueryVisible(false)}>确认</Button>
        </Modal.Footer>
      </Modal>
    </>
  );
}

type ChainParamWithUnique<T> = T & { uid: string };

function initChainParamsRow<T>(paramKeys: any[]): ChainParamWithUnique<T> {
  return {
    uid: moment().toISOString(),
    ...paramKeys.reduce((res, k) => {
      if (k === 'MethodFunc') {
        res[k] = 'invoke';
      } else {
        res[k] = '';
      }
      return res;
    }, {}),
  };
}

function initChainParamsRowWithDefaultValues<T>(values: T[]): ChainParamWithUnique<T>[] {
  return values.map((item) => ({
    uid: moment().toISOString(),
    ...item,
  }));
}

export function ChainParamsText<T>(props: { values: T[] }) {
  return (
    <>
      {props.values.map((item, index: number) => (
        <div key={index} className={'tea-mt-2n'}>
          {Object.keys(item as unknown as object).map((k) => (
            <>
              <Input defaultValue={(item as any)[k]} className={'tea-mr-1n'} disabled />
            </>
          ))}
        </div>
      ))}
    </>
  );
}

/**
 * 链参数输入组件
 */
function ChainParamsInputContainer<T>(
  props: {
    onChange: (params: T[]) => void;
    paramKeys: (string | number | symbol)[];
    readonly?: boolean;
    defaultValues?: T[];
    placeholders?: string[];
  },
  ref: Ref<any>,
) {
  const [paramItems, setParamItems] = useState<ChainParamWithUnique<T>[]>(
    props.defaultValues?.length
      ? initChainParamsRowWithDefaultValues(props.defaultValues)
      : [initChainParamsRow<T>(props.paramKeys)],
  );
  const [readonly, setReadonly] = useState(props.readonly);

  const updateParamItems = useCallback((params: ChainParamWithUnique<T>[]) => {
    setParamItems(params);
    // 针对空行进行过滤
    props.onChange(
      params
        .filter((item) => props.paramKeys.some((k) => (item as any)[k]))
        .map((item) => pick(item, props.paramKeys as any)),
    );
  }, []);

  const handleParamAdd = useCallback(() => {
    updateParamItems([...paramItems, initChainParamsRow<T>(props.paramKeys)]);
  }, [paramItems]);

  const handleParamDelete = useCallback(
    (index: number) => {
      paramItems.splice(index, 1);
      updateParamItems([...paramItems]);
    },
    [paramItems],
  );
  const handleParamBlur = useCallback(
    (index: number, prop: string, value: string) => {
      const newParams = [...paramItems];
      (newParams[index] as any)[prop] = value.trim();
      updateParamItems(newParams);
    },
    [paramItems],
  );

  useEffect(() => {
    setReadonly(props.readonly);
  }, [props.readonly]);

  useImperativeHandle(ref, () => ({
    updateParams: (items: T[]) => {
      setParamItems(
        items.length ? initChainParamsRowWithDefaultValues(items) : [initChainParamsRow<T>(props.paramKeys)],
      );
    },
  }));

  return (
    <>
      {paramItems.map((item, index: number) => (
        <div key={item.uid + index} className={'tea-mt-2n'} style={{ display: 'flex' }}>
          {Object.keys(pick(item, props.paramKeys as any)).map((k, columnIndex) =>
            k !== 'MethodFunc' ? (
              <Input
                readonly={readonly && !columnIndex}
                placeholder={props.placeholders?.[columnIndex] ?? k}
                defaultValue={(item as any)[k]}
                className={'tea-mr-1n'}
                onBlur={(e) => handleParamBlur(index, k, e.target.value)}
              />
            ) : (
              <Select
                className={'tea-mr-1n'}
                onChange={(val) => handleParamBlur(index, k, val)}
                options={methodFuncList}
                defaultValue={(item as any)[k]}
              />
            ),
          )}
          {index === 0 ? (
            <Button onClick={handleParamAdd} type={'primary'} disabled={readonly}>
              增加
            </Button>
          ) : (
            <Button onClick={() => handleParamDelete(index)} disabled={readonly}>
              删除
            </Button>
          )}
        </div>
      ))}
    </>
  );
}

// TODO，这里出现了类型不安全，高阶函数造成范型类型信息丢失
export const ChainParamsInput = React.forwardRef(ChainParamsInputContainer) as any;
